#-*- coding: utf-8 -*-

#This file is part of pyLot library.
#
# pyLot is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyLot is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with pyLot.  If not, see <http://www.gnu.org/licenses/>.

__author__ = u'Pierre Puiseux, Guillaume Baty'
__copyright__ = u"Copyright 2011-2012 (C) andheo, Université de Pau et des Pays de l'Adour"
__credits__ = [u'Pierre Puiseux', u'Guillaume Baty']
__license__ = "GNU Lesser General Public License"

__all__ = ['DangerousUnitOperation', 'Quantity', 'UNITS']


import logging
import quantities as pq
from _units import UNITS


# Setup a logger for this module.
logger = logging.getLogger(__name__)

#  raise IncompatibleUnits("add", self, other)

class DangerousUnitOperation(Exception):
  def __init__(self, operator, unit1, unit2):
    self._op = operator
    self._units = [unit1, unit2]
    return

  def __str__(self):
    str = "Cannot use operator '%s' with '%s' and '%s'" % tuple([
      self._op] + self._units)
    return str

_base_units = [
    pq.UnitCurrent.get_default_unit(),
    pq.UnitLength.get_default_unit(),
    pq.UnitLuminousIntensity.get_default_unit(),
    pq.UnitMass.get_default_unit(),
    pq.UnitSubstance.get_default_unit(),
    pq.UnitTemperature.get_default_unit(),
    pq.UnitTime.get_default_unit(),
]

dangerous = ['degr', 'degc', 'degf', 'fahrenheit', 'celsius']

_base_unit_dict = {}
for u in _base_units :
  _base_unit_dict[u.name] = u

#class Unit (TraitType):
#    """ Defines a trait whose value must be a Python float.
#    """
#    # The function to use for evaluating strings to this type:
#    evaluate = pq.UnitQuantity
#
#    # A description of the type of value this trait accepts:
#    info_text = 'an unit'
#
#    def validate (self, object, name, value):
#        """ Validates that a specified value is valid for this trait.
#        """
#        if isinstance(value, pq.Quantity):
#          return value
#        if isinstance(value, (numpy.ndarray, float, int)):
#          return pq.UnitQuantity(float(value) * pq.dimensionless)
#
#        self.error(object, name, value)


class Quantity(object):
  u"""
  Trait class for units using quantities package

  .. warning ::
  
    Please read this doc entirely before using this class

  **Equality test**

  Testing equality may returns unexpected results because two quantities 
  are considered equal if values and names are identical.

  >>> from quantities import cm,m,kt,s
  >>> from pyLot.physics import Quantity
  >>> qt1 = Quantity(250*cm)
  >>> qt2 = Quantity(2.5*m)
  >>> qt1 == qt2
  True
  >>> qt1 is qt2
  False
  >>> qt1 = Quantity(2.5*m, name='L1')
  >>> qt2 = Quantity(2.5*m, name='L2')
  >>> qt1 == qt2
  False
  
  To check only values compare "data" instead of "value" because value has
  no dimensions.
  
  Example :
  
  >>> eps = 1e-5
  >>> abs(qt1.data-qt2.data) < eps
  True

  DO NOT USE "Quantity.value" because it has no information about units.
  Example :
  
  >>> abs(Quantity(250*cm).value - Quantity(2.5*m).value) < eps
  True
  
  Great ... but :
  
  >>> abs(Quantity(2.5*m).value - Quantity(4.85961123*kt).value) < eps
  True
  >>> abs(Quantity(2.5*m).value - Quantity(2.5*m/s).value) < eps
  True

  Instead, using "data" raises an error

  >>> abs(Quantity(2.5*m).data - Quantity(2.5*m/s).data) < eps
  Traceback (most recent call last):
  ...
  ValueError: Unable to convert between units of "m/s" and "m"

  **Coordinate systems and relative units**
  
  This class works well with absolute unit only !

  For example, °C cannot be handled correctly
  If we are talking about temperature variations :
    20K+20°C = 40°C = 40K !
  If we are talking about absolute temperatures :
    20K+20°C = -253.15°C = 40K !

  **Currently, only relative temperatures raise errors**

  >>> from quantities import degC, K
  >>> Quantity(20*K)+Quantity(20*degC)
  Traceback (most recent call last):
  ...
  DangerousUnitOperation: Cannot use operator '+' with 'K' and 'degC'

  """
  def setUnit(self, unit):
    self.data.units = unit

  name = u''
  data = None

  value = property(fget=lambda self:self.data.simplified.magnitude)
  symbol = property(fget=lambda self:self.data.dimensionality.unicode)
  unit = property(fget=lambda self:self.data.units, fset=setUnit)

  def _chekOperator(self, op, other):
    is_dangerous = False
    for k in self.unit.simplified.dimensionality :
      if isinstance(k, pq.UnitTemperature):
        is_dangerous = True

    if is_dangerous is False :
      return

    if self.symbol != other.symbol :
      raise DangerousUnitOperation(op, k.symbol, other.symbol)

  def __init__(self, unit_obj, name=None):
    super(Quantity, self).__init__()
    self.data = unit_obj
    self.name = name

  def __eq__(self, other):
    return (self.data == other.data and self.name == other.name)

  # TODO: I don't think these will work for derived classes...
  def __neg__(self):
    return self.__class__(-self.data, name=self.name)

  def __abs__(self):
    name = '|%s|' % self.name if self.name else ''
    return self.__class__(abs(self.data), name=name)

  def __add__(self, other):
    self._chekOperator('+', other)
    return self.__class__(self.data + other.data, name=self.name)

  def __sub__(self, other):
    self._chekOperator('-', other)
    return self.__class__(self.data - other.data, name=self.name)

  def __mul__(self, other):
    self._chekOperator('*', other)
    return self.__class__(self.data * other.data)

  def __div__(self, other):
    self._chekOperator('/', other)
    return self.__class__(self.data / other.data)

  def __pow__(self, other):
    return self.__class__(self.data ** other)

  def __repr__(self):
    """ Return the string representation of this quantity. """
    return '%s(%r, name=%r)' % \
          (self.__class__.__name__, self.data, self.name)



if __name__ == '__main__' :
  import doctest
  doctest.testmod()
